package com.letoo.sso.core.service.impl;

import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

//import com.letoo.dragon.rpc.client.InitializeUsercenterRPCService;
//import com.letoo.dragon.rpc.entity.InitializeUserRequest;
//import com.letoo.notification.rpc.client.UserDeviceRPCService;
import com.letoo.sso.common.domain.ContextConstants;
import com.letoo.sso.common.domain.Result;
import com.letoo.sso.common.domain.ResultCode;
import com.letoo.sso.common.domain.SensitiveWordsResult;
import com.letoo.sso.common.exception.BusinessException;
import com.letoo.sso.common.utils.AESUtils;
import com.letoo.sso.common.utils.AssertUtil;
import com.letoo.sso.common.utils.DateUtil;
import com.letoo.sso.common.utils.MD5Utils;
import com.letoo.sso.common.utils.MSensitiveWordsUtils;
import com.letoo.sso.common.utils.RegexUtils;
import com.letoo.sso.common.utils.TokenGenerator;
import com.letoo.sso.core.service.AppBaseConfigService;
import com.letoo.sso.core.service.CounterService;
import com.letoo.sso.core.service.LoginFailureLogService;
import com.letoo.sso.core.service.NewUserService;
import com.letoo.sso.core.service.SystemCodeService;
import com.letoo.sso.core.service.UserService;
import com.letoo.sso.core.service.UserTokenService;
import com.letoo.sso.dataaccess.dao.cahce.CaptchaCacheDAO;
import com.letoo.sso.dataaccess.dao.cahce.UserTokenCacheDAO;
import com.letoo.sso.dataaccess.dao.db.BaseDBDAO;
import com.letoo.sso.dataaccess.dao.db.RegisterDeviceDBDAO;
import com.letoo.sso.dataaccess.dao.db.UserBindThirdPartyDBDAO;
import com.letoo.sso.dataaccess.dao.db.UserDBDAO;
import com.letoo.sso.dataaccess.dao.db.UserTokenDBDAO;
import com.letoo.sso.dataaccess.domain.dbo.LoginFailureLogDO;
import com.letoo.sso.dataaccess.domain.dbo.NewUserDO;
import com.letoo.sso.dataaccess.domain.dbo.RegisterDeviceDO;
import com.letoo.sso.dataaccess.domain.dbo.SystemCodeDO;
import com.letoo.sso.dataaccess.domain.dbo.UserBindThirdPartyDO;
import com.letoo.sso.dataaccess.domain.dbo.UserDO;
import com.letoo.sso.dataaccess.domain.dbo.UserTokenDO;
import com.letoo.sso.dataaccess.domain.mo.RegisterUserMO;
import com.letoo.sso.dataaccess.domain.mo.ThirdPartyRegisterUserMO;

/**
 * SSO用户服务接口实现
 *
 * @author: dengjie
 */
@Service("userService")
public class UserServiceImpl extends BaseServiceImpl<UserDO> implements UserService {

    /**
     * 日志
     */
    private final Logger log = LoggerFactory.getLogger(getClass());
    public static final String INVITER_MATCH = "^[abAB]\\d{3,4}$";

    @Resource
    private UserDBDAO userDBDAO;
    @Resource
    private CounterService counterService;
    @Resource
    private CaptchaCacheDAO captchaCacheDAO;
    @Resource
    private UserTokenCacheDAO userTokenCacheDAO;
    @Resource
    private UserTokenDBDAO userTokenDBDAO;
    @Resource
    private TaskExecutor userTokenDbSaveTaskExecutor;
    @Resource
    private UserTokenService userTokenService;
    @Resource
    private SystemCodeService systemCodeService;
    @Resource
    private LoginFailureLogService loginFailureLogService;
    @Resource
    private AppBaseConfigService appBaseConfigService;
    // @Resource
    // private InitializeUsercenterRPCService initializeUsercenterRpcService;
    // @Resource
    // private UserDeviceRPCService userDeviceRPCService;
    @Resource
    private NewUserService newUserService;
    @Resource
    private UserBindThirdPartyDBDAO userBindThirdPartyDBDAO;
    @Resource
    private RegisterDeviceDBDAO registerDeviceDBDAO;

    @Override
    protected BaseDBDAO<UserDO> getDAO() {
        return userDBDAO;
    }

    private void validateRegisterInfo(RegisterUserMO registerUserMO) {
        // 校验手机设备的唯一性。业务：A设备注册了1号用户，2号用户就不能在A设备上进行注册。以后进行建议流程优化。
        String clientId = registerUserMO.getClientId();
        if (StringUtils.isBlank(clientId)) {
            log.error("validateRegisterInfo method clientId is blank");
        }
        int countRows = registerDeviceDBDAO.isExistClientId(clientId);
        if (countRows >= 1) {
            // 已经存在的clientId则不能进行注册。
            throw new BusinessException(ResultCode.SSO_REGISTER_CLIENTID_IS_EXISTED,
                    "register clientId is existed,the clientId is：" + clientId);
        }
        // 校验手机设备注册唯一性
        // 校验手机号码
        String mobile = registerUserMO.getMobile();
        if (StringUtils.isBlank(mobile) || !RegexUtils.Mobile(mobile)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_MOBILE_IS_ILLEGAL,
                    "register mobile is illegal,the mobile is：" + mobile);
        }
        // 校验验证码
        String captcha = registerUserMO.getCaptcha();
        if (StringUtils.isBlank(captcha)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_CAPTCHA_IS_ILLEGAL,
                    "register captcha is illegal,the captcha is：" + captcha);
        }
        // 这里需要调用redis来验证captcha是不是发给这个手机号的验证码
        String captchaCache = captchaCacheDAO.getRegisterCaptcha(mobile);
        if (!StringUtils.equals(registerUserMO.getCaptcha(), captchaCache)) {
            // 如果相当那么校验通过
            throw new BusinessException(ResultCode.SSO_REGISTER_CAPTCHA_IS_ILLEGAL,
                    "register captcha is illegal,the captcha is：" + captcha);
        }

        // 校验密码
        String pwd = registerUserMO.getPassword();
        if (StringUtils.isBlank(pwd)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_PASSWORD_IS_ILLEGAL,
                    "register password is illegal,the password is：" + pwd);
        }
        // 校验头像不能为空
        String headPortrait = registerUserMO.getHeadPortrait();
        if (StringUtils.isBlank(headPortrait)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_HEADPORTRAIT_IS_ILLEGAL,
                    "register headPortrait is illegal,the headPortrait is：" + headPortrait);
        }
        // 校验昵称
        String nickname = registerUserMO.getNickname();
        if (StringUtils.isBlank(nickname)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_NICKNAME_IS_ILLEGAL,
                    "register nickname is illegal,the nickname is：" + nickname);
        }
        /** 需要对接的关键词过滤进行判定。 **/
        // 敏感词校验并且过滤
        SensitiveWordsResult resNickname = MSensitiveWordsUtils.checkSensitiveWords(nickname);
        if (!resNickname.isStatus() && resNickname.getCode() == 1) {
            throw new BusinessException(ResultCode.SSO_REGISTER_NICKNAME_IS_ILLEGAL,
                    "register nickname is illegal,have sensitive words,the nickname is：" + nickname);
        }
        /** 需要对接银哥的关键词过滤进行判定。 **/
        // 校验生日，生日不能比当前时间大。保证年龄不能为负数
        Date birthday = registerUserMO.getBirthday();
        // 当前日期
        Date curDate = new Date();
        if (birthday.after(curDate)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_BIRTHDAY_IS_ILLEGAL,
                    "register birthday is illegal,the birthday is：" + birthday);
        }
        curDate = null;
        // 校验性别只能是0或者1
        int gender = registerUserMO.getGender();
        if (gender != 0 && gender != 1) {
            throw new BusinessException(ResultCode.SSO_REGISTER_GENDER_IS_ILLEGAL,
                    "register gender is illegal,the gender is：" + gender);
        }
        // 校验情感状态
        int feelingStatus = registerUserMO.getFeelingStatus();
        if (feelingStatus <= 0) {
            throw new BusinessException(ResultCode.SSO_REGISTER_FEELINGSTATUS_IS_ILLEGAL,
                    "register feelingStatus is illegal,the feelingStatus is：" + feelingStatus);
        }

        // 将邀请码转换成小写（刘银）
        String inviterMiaoId = registerUserMO.getInviterMiaoId();
        if (StringUtils.isNotBlank(inviterMiaoId))
            registerUserMO.setInviterMiaoId(inviterMiaoId.toUpperCase());

    }

    private void setDefaultValue(RegisterUserMO registerUserMO) {
        // 设置默认的头像名字
        registerUserMO.setHeadPortrait("headportraitdefault.png");
        // 设置默认昵称
        registerUserMO.setNickname("桌游英雄");
        // 设置默认性别为男
        registerUserMO.setGender(1);
        registerUserMO.setFeelingStatus(1);
        registerUserMO.setBirthday(DateUtil.getDate(
                DateUtil.getFormatDateAdd(new Date(), -22 * 365, "yyyy-MM-dd HH:mm:ss"), "yyyy-MM-dd HH:mm:ss"));
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public Result register(RegisterUserMO registerUserMO) {
        // 因为注册有些信息是默认的，故需要进行设置初始化值
        setDefaultValue(registerUserMO);
        validateRegisterInfo(registerUserMO);
        // 校验通过则进行注册流程
        // 为用户自动生成bgId和userId
        long bgId = counterService.nextCount(CounterService.KeyType.BG_ID);
        long userId = counterService.nextCount(CounterService.KeyType.USER_ID);

        // 创建sso t_user相关信息
        UserDO userDO = new UserDO();
        userDO.setBgId(String.valueOf(bgId));
        userDO.setUserId(userId);
        userDO.setMobile(registerUserMO.getMobile());
        // 用户注册的时候客户端传入的是加密后的明文密码，所以存入数据库需要进行解密并且MD5BASE64摘要。
        String pwdSecretKey = appBaseConfigService.getAppBaseConfig(ContextConstants.APP_ID,
                ContextConstants.PASSWORD_SECRET_KEY);
        log.info("dengjie,app send pwd is:" + registerUserMO.getPassword());
        log.info("dengjie,pwdSecretKey is:" + pwdSecretKey);
        String pwdPlaintext = AESUtils.decrypt(registerUserMO.getPassword(), pwdSecretKey);
        log.info("dengjie,pwdPlaintext is:" + pwdPlaintext);
        String pwdPlaintextMD5 = MD5Utils.md5AndBase64(pwdPlaintext);
        log.info("dengjie,pwdPlaintextMD5 is:" + pwdPlaintextMD5);
        userDO.setPassword(pwdPlaintextMD5);
        userDO.setRegisterIp(registerUserMO.getRegisterIp());
        userDO.setPhoneType(registerUserMO.getPhoneType());
        userDO.setLon(registerUserMO.getLon());
        userDO.setLat(registerUserMO.getLat());
        userDO.setRegisterAddress(registerUserMO.getRegisterAddress());
        userDO.setOriginType(registerUserMO.getOriginType());
        userDO.setDeviceType(registerUserMO.getDeviceType());
        userDO.setInviterMiaoId(registerUserMO.getInviterMiaoId());
        userDO.setEnabled(UserDO.USER_ENABLED_OK);
        userDO.setLoginFrequency(0);
        userDO.setOnlineStatus(UserDO.USER_ONLINE_STATUS_OFF);
        userDBDAO.save(userDO);

        // 保存注册设备clientId（个推）
        saveRegisterDevice(registerUserMO.getClientId());
        // RPC创建用户中心系统、判断是否自动加邀请人为好友。
        // TODO dengjie 以后修改成内部接口调用
        // initializeUsercenter(userId, bgId, registerUserRequest);
        // RPC创建用户中心系统、判断是否自动加邀请人为好友。

        // 初始化新注册用户表信息。用来判断是否引导认识流程。
        initNewUserInfo(userId);

        Result res = new Result(ResultCode.COMMON_SUCCESS, true);
        res.setProperty("bgId", userDO.getBgId());
        res.setProperty("pwd", userDO.getPassword());
        return res;
    }

    /**
     * @param clientId
     * @description 注册设备记录
     * @author dengjie
     */
    private void saveRegisterDevice(String clientId) {
        if (StringUtils.isBlank(clientId))
            return;

        RegisterDeviceDO registerDeviceDO = new RegisterDeviceDO();
        registerDeviceDO.setClientId(clientId);
        registerDeviceDBDAO.save(registerDeviceDO);
    }

    /**
     * 初始化新用户是否是第一次注册用户表。
     *
     * @param userId
     *            用户ID
     */
    private void initNewUserInfo(long userId) {
        NewUserDO newUserDO = new NewUserDO();
        newUserDO.setUserId(userId);
        newUserDO.setFirstRegistration(true);
        newUserDO.setFinishPutawayGuidance(false);
        newUserService.saveNewUser(newUserDO);
        newUserDO = null;
    }

    /**
     * 用户中心系统和判断是否自动加邀请人为好友。默认是有邀请人的时候，就把邀请人自动跟注册用户加好友。
     * 这个地方要注意区别特殊的邀请码，推广账号的邀请码是不用加好友的。推广账号的邀请码格式是：50起到999.正常用户是：1000起计算miaoId。 官方账号是：1到50
     *
     * @param userId
     *            注册当前用户userId
     * @param bgId
     *            注册当前用户bgId
     * @param registerUserRequest
     *            注册表单信息
     */
    // TODO dengjie 以后修改成内部接口调用
    // private void initializeUsercenter(long userId, long bgId, RegisterUserRequest registerUserRequest) {
    // InitializeUserRequest initializeUserRequest = new InitializeUserRequest();
    // initializeUserRequest.setUserId(userId);
    // initializeUserRequest.setBgId(bgId);
    // initializeUserRequest.setMobile(registerUserRequest.getMobile());
    // initializeUserRequest.setHeadPortrait(registerUserRequest.getHeadPortrait());
    // initializeUserRequest.setNickname(registerUserRequest.getNickname());
    // initializeUserRequest.setGender(registerUserRequest.getGender());
    // initializeUserRequest.setBirthday(registerUserRequest.getBirthday());
    // initializeUserRequest.setFeelingStatus(registerUserRequest.getFeelingStatus());
    // // 查询用户表中的userId，根据邀请人是手机号码或者miaoId来查询得到邀请人的userId，这个需要初始化给UC系统存入inviterUserId字段。
    // // 判断用户输入的是手机号码还是miaoId.查询出来我们邀请人的userId。
    // String inviterMiaoId = registerUserRequest.getInviterMiaoId();
    // // 查询邀请人ID TODO dengjie
    // Long inviterUserId = getInviterUserIdByinviterMiaoId(inviterMiaoId);
    // if (inviterUserId == null) {
    // inviterUserId = 0L;
    // }
    // initializeUserRequest.setInviterUserId(inviterUserId);
    //
    // com.letoo.dragon.rpc.entity.Result resRpc =
    // initializeUsercenterRpcService.initializeUsercenter(initializeUserRequest);
    // if (!resRpc.isSuccess()) {
    // throw new BusinessException(ResultCode.COMMON_BUSINESS_EXCEPTION, "register usercenter is error,userId is:" +
    // userId);
    // }
    // }

    private Long getInviterUserIdByinviterMiaoId(String inviterMiaoId) {
        if (StringUtils.isBlank(inviterMiaoId))
            return 0L;

        if (RegexUtils.Mobile(inviterMiaoId)) {
            return userDBDAO.queryUserIdByMobile(inviterMiaoId);
        } else {
            return userDBDAO.queryUserIdByMiaoId(inviterMiaoId);
        }
    }

    @Override
    public Result mobileIsExisted(String mobile) {
        // 校验手机号码
        if (StringUtils.isBlank(mobile) || !RegexUtils.Mobile(mobile)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_MOBILE_IS_ILLEGAL,
                    "register mobile is illegal,the mobile is：" + mobile);
        }
        int rows = userDBDAO.mobileIsExisted(mobile);

        Result result = new Result(ResultCode.COMMON_SUCCESS, true);
        if (rows > 0) {
            return result;
        } else {
            result.setResultCode(ResultCode.COMMON_FAILED);
            result.setSuccess(false);
            return result;
        }
    }

    private void validateUserStatusAndPwd(UserDO user, String password) {
        int enabled = user.getEnabled();
        int loginFrequency = user.getLoginFrequency();
        Date lastLoginTime = user.getLastLoginTime();
        // 查询系统代码，找到系统代码中的“登陆限制-登陆失败次数”，“登陆显示-登陆失败允许重新登陆时间长度”
        // 切换到redis中进行查询
        Map<String, List<SystemCodeDO>> mapConfig = systemCodeService.getSystemCodeMapWithList(
                ContextConstants.SC_LOGIN_LIMIT_FAILURE_FREQUENCY, ContextConstants.SC_LOGIN_LIMIT_RELOGIN_PERIOD);
        int limitFailureFrequency = Integer
                .valueOf(mapConfig.get(ContextConstants.SC_LOGIN_LIMIT_FAILURE_FREQUENCY).get(0).getCodeValue());
        int limitReloginPeriod = Integer
                .valueOf(mapConfig.get(ContextConstants.SC_LOGIN_LIMIT_RELOGIN_PERIOD).get(0).getCodeValue());
        if (enabled == 0) {
            // 账号被禁用
            loginFailureOp(user, ResultCode.SSO_LOGIN_USER_IS_DISABLED.getDescription(), 2, loginFrequency,
                    limitFailureFrequency);
            throw new BusinessException(ResultCode.SSO_LOGIN_USER_IS_DISABLED,
                    "user is disabled,the userId is:" + user.getUserId());
        }

        long diffTime = DateUtil.getDiff(new Date(), lastLoginTime);
        if (loginFrequency >= limitFailureFrequency && diffTime < limitReloginPeriod) {
            // 账号登陆失败次数过多被禁用
            loginFailureOp(user, ResultCode.SSO_LOGIN_USER_FAILED_NUMBER_IS_TOO_MUCH.getDescription(), 4,
                    loginFrequency, limitFailureFrequency);
            throw new BusinessException(ResultCode.SSO_LOGIN_USER_FAILED_NUMBER_IS_TOO_MUCH,
                    "the user login failed numbers is too much,the userId is:" + user.getUserId());
        }
        // 校验密码
        String pwdInServer = user.getPassword();
        if (!StringUtils.equals(pwdInServer, password)) {
            // 账号输入密码错误
            loginFailureOp(user, ResultCode.SSO_LOGIN_USER_PWD_IS_ERROR.getDescription(), 1, loginFrequency,
                    limitFailureFrequency);
            throw new BusinessException(ResultCode.SSO_LOGIN_USER_PWD_IS_ERROR,
                    "the password is error,the userId is:" + user.getUserId() + " the write password is:" + password);
        }

    }

    /**
     * 登陆失败记录log并且更新user表的失败次数+1,并且修改最后登陆时间为现在
     *
     * @param user
     * @param reason
     * @param type
     * @param loginFrequency
     * @param limitFailureFrequency
     */
    private void loginFailureOp(UserDO user, String reason, int type, int loginFrequency, int limitFailureFrequency) {
        if (loginFrequency < limitFailureFrequency) {
            // 要在登陆次数小于限制次数的时候再去更新登陆失败次数和最后登陆时间。因为不然这样30分钟就不起作用了。
            userDBDAO.updateLastLoginTimeAndLoginFrequency(user.getUserId());
        }
        // 记录登陆失败记录表
        LoginFailureLogDO loginFailureLog = new LoginFailureLogDO();
        loginFailureLog.setDeviceType(user.getDeviceType());
        loginFailureLog.setPhoneType(user.getPhoneType());
        loginFailureLog.setReason(reason);
        loginFailureLog.setType(type);
        loginFailureLog.setUserId(user.getUserId());
        loginFailureLogService.save(loginFailureLog);
        loginFailureLog = null;
    }

    @Override
    public Result login(final String mobileOrBgId, final String password, final String clientId) {
        AssertUtil.isNotBlank(mobileOrBgId, "mobileOrBgId is null or blank");
        AssertUtil.isNotBlank(password, "password is null or blank");
        // 由于个推的问题，clientId不能获取到那么这个不能禁止用户去登陆。该记录日志。
        if (StringUtils.isBlank(clientId)) {
            log.error("user login error.the clientId is blank.the mobileOrBgId is:" + mobileOrBgId);
        }
        Result result = new Result(ResultCode.COMMON_SUCCESS, true);
        UserDO user = null;
        // 判断用户输入的是手机号码还是miaoId
        if (RegexUtils.Mobile(mobileOrBgId)) {
            // 用户输入的是手机号码.
            // 走DB查询缓存用户信息
            user = userDBDAO.queryUserByMobile(mobileOrBgId);
        } else {
            user = userDBDAO.queryUserByMiaoId(mobileOrBgId);
        }

        // 用户名密码正确查询出来用户了，创建userId和token的关系并且存入缓存。暂时不设置过期时间，以后如果要做过期功能则再进行设置。
        if (user != null) {
            // 校验user的账号状态是否禁用、登陆失败次数是否超过要求等限制条件、登陆密码是否正确
            validateUserStatusAndPwd(user, password);

            final String token = TokenGenerator.generateValue();
            final long userId = user.getUserId();
            // 写入缓存中，采用强制覆盖的方式set进入
            userTokenCacheDAO.setUserToken(userId, token);
            // 通过更新通知系统的clientId
            // 初始化更新维护用户推送相关信息
            // TODO dengjie 以后修改成内部接口调用
            // userDeviceRPCService.createUserDeviceInfo(userId, clientId, 0);
            // 通过更新通知系统的clientId
            // 方便排查错误并且保证用户登陆状态安全，记录该用户的token钥匙在DB中，保证touchToken校验的时候缓存出现故障也能保证其校验通过提高用户体验
            saveDBInfoForLoginBiz(userId, token, clientId);
            result.setProperty("userId", userId);
            result.setProperty("token", token);
            // 新增判定该用户是否是第一次注册的新用户
            boolean isFirstRegistration = newUserService.queryNewUserByUserId(userId);
            result.setProperty("isFirstRegistration", isFirstRegistration);
            // 新增判定用户是否完成上架引导
            // boolean isFinishPutawayGuidance = newUserService.queryIsFinishPutawayGuidanceByUserId(userId);
            // result.setProperty("isFinishPutawayGuidance", isFinishPutawayGuidance);
        } else {
            throw new BusinessException(ResultCode.SSO_LOGIN_ACCOUT_IS_NOT_EXISTED,
                    "username or password is illegal,the username is：" + mobileOrBgId + ",password is: " + password);
        }
        return result;
    }

    /**
     * 异步操作。保存对应DB信息和更新推送系统中的clientId
     *
     * @param userId
     *            用户ID
     * @param token
     *            用户token
     * @param clientId
     *            用户推送clientId
     */
    private void saveDBInfoForLoginBiz(final long userId, final String token, final String clientId) {
        try {
            userTokenDbSaveTaskExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    UserTokenDO ut = new UserTokenDO(userId, token);
                    userTokenService.saveOrUpdate(ut);
                    // 更新用户最后登陆时间和重置登陆失败次数及在线状态
                    userDBDAO.updateLastLoginTimeAndLoginFrequencyAndOnlineStatus(0, userId, 1);
                    ut = null;
                }
            });
        } catch (Exception e) {
            log.error("userTokenDbSaveTaskExecutor is error", e);
            throw new BusinessException(ResultCode.COMMON_SYSTEM_EXCEPTION, "userTokenDbSaveTaskExecutor is error");
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public Result logout(final long userId) {
        log.info("The logout's userId is:" + userId);
        // 先删除DB中userToken关系
        int rows = userTokenDBDAO.deleteByUserId(userId);
        if (rows != 1) {
            //
            log.error("userId logout failed,the userId is：" + userId);
            // throw new BusinessException(ResultCode.SSO_LOGOUT_FAILED, "userId
            // logout failed,the userId is：" +
            // userId);
        }
        // 更新t_user表在线状态和更新时间
        userDBDAO.updateOnlineStatus(UserDO.USER_ONLINE_STATUS_OFF, userId);

        // 删除缓存中的userToken关系
        userTokenCacheDAO.deleteByUserId(userId);
        // 用户退出的时候，需要清空推送的userId和clientId的关系
        // TODO dengjie 以后修改成内部接口调用
        // userDeviceRPCService.removeUserDeviceInfo(userId);
        Result result = new Result(ResultCode.COMMON_SUCCESS, true);
        return result;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public Result updatePassword(final long userId, final String originalPassword, final String newPassword) {
        AssertUtil.gt(userId, 0l, "userId must gt zero,the userId is:" + userId);
        AssertUtil.isNotBlank(originalPassword, "originalPassword must be not blank");
        AssertUtil.isNotBlank(newPassword, "newPassword must be not blank");
        // 获取用户当前密码
        UserDO curUser = userDBDAO.queryUserByUserId(userId);
        if (curUser == null) {
            throw new BusinessException(ResultCode.SSO_USER_IS_NOT_EXISTED,
                    "userId is not existed,the userId is：" + userId);
        }
        //
        String pwdSecretKey = appBaseConfigService.getAppBaseConfig(ContextConstants.APP_ID,
                ContextConstants.PASSWORD_SECRET_KEY);
        String originalPasswordTmp = AESUtils.decrypt(originalPassword, pwdSecretKey);
        String newPasswordTmp = AESUtils.decrypt(newPassword, pwdSecretKey);
        // 用户存在，则校验输入密码信息
        if (!StringUtils.equals(curUser.getPassword(), MD5Utils.md5AndBase64(originalPasswordTmp))) {
            /* 输入的原密码与当前密码不一致 */
            throw new BusinessException(ResultCode.SSO_UPDATEPWD_ORIGINALPWD_NOT_EQUALS,
                    "userId update password failed,the userId is：" + userId);
        } else if (StringUtils.equals(originalPasswordTmp, newPasswordTmp)) {
            /* 输入的新密码与原密码一致 */
            throw new BusinessException(ResultCode.SSO_UPDATEPWD_ORIGINALPWD_EQUALS_NEWPWD,
                    "userId update password failed,the userId is：" + userId);
        } else {
            // 校验通过后，修改DB中密码。返回修改成功结果给客户端.客户端会跳转到登陆页面进行重新登陆。
            // 该密码必须是客户端进行MD5和BASE64后的值进行网络传输
            userDBDAO.updatePassword(userId, MD5Utils.md5AndBase64(newPasswordTmp));
            Result result = new Result(ResultCode.COMMON_SUCCESS, true);
            return result;
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public Result updatePasswordWhenForget(final String mobile, final String captcha, final String newPassword) {
        AssertUtil.isNotBlank(mobile, "mobile must be not blank");
        AssertUtil.isNotBlank(captcha, "captcha must be not blank");
        AssertUtil.isNotBlank(newPassword, "newPassword must be not blank");
        // 先校验发送的验证码是否正确
        String cachedCaptcha = captchaCacheDAO.getPasswordCaptcha(mobile);
        if (StringUtils.isBlank(cachedCaptcha)) {
            /* 验证码过期 */
            throw new BusinessException(ResultCode.SSO_FORGET_UPDATEPWD_CAPTCHA_IS_EXPIRED,
                    "the captch is expired,the mobile is：" + mobile);
        } else if (!StringUtils.equals(cachedCaptcha, captcha)) {
            /* 输入的验证码与缓存的验证码不匹配 */
            throw new BusinessException(ResultCode.SSO_FORGET_UPDATEPWD_CAPTCHA_IS_NOT_EQUALS,
                    "the captch is not equals with cache,the mobile is：" + mobile);
        } else {
            // 校验通过查询当前用户是否存在，存在才进行密码修改。
            UserDO user = userDBDAO.queryUserByMobile(mobile);
            if (user == null) {
                throw new BusinessException(ResultCode.SSO_USER_IS_NOT_EXISTED,
                        "the user is not existed,the mobile is：" + mobile);
            } else {
                //
                String pwdSecretKey = appBaseConfigService.getAppBaseConfig(ContextConstants.APP_ID,
                        ContextConstants.PASSWORD_SECRET_KEY);
                // 该密码必须是客户端进行AES加密后的值进行网络传输
                userDBDAO.updatePassword(user.getUserId(),
                        MD5Utils.md5AndBase64(AESUtils.decrypt(newPassword, pwdSecretKey)));
                // 修改密码完成后，需要删除创建缓存中的验证码
                captchaCacheDAO.deletePasswordCaptcha(mobile);
                Result result = new Result(ResultCode.COMMON_SUCCESS, true);
                return result;
            }
        }
    }

    @Override
    public Result checkInviter(String inviter) {
        boolean legal = false;
        if (StringUtils.isBlank(inviter)) {
            legal = true;
        } else {
            if (inviter.matches(INVITER_MATCH)) {
                // TODO dengjie 以后修改成内部接口调用
                // legal = initializeUsercenterRpcService.getUserIdByInvitationCode(inviter) != null;
            } else if (RegexUtils.Mobile(inviter)) {
                legal = userDBDAO.queryUserByMobile(inviter) != null;
            } else {
                legal = userDBDAO.queryUserByMiaoId(inviter) != null;
            }
        }
        Result result;
        if (legal) {
            result = new Result(ResultCode.COMMON_SUCCESS, true);
        } else {
            result = new Result(ResultCode.SSO_ILLEGAL_INVITER, false);
        }

        return result;

    }

    private void validateThirdPartyRegisterInfo(ThirdPartyRegisterUserMO registerUserMO) {
        // 校验type是否是1、2、3。
        int type = registerUserMO.getType();
        if (type < 1 || type > 3) {
            throw new BusinessException(ResultCode.COMMON_PARAMETER_ERROR,
                    "register type is illegal,the type is：" + type);
        }
        // 校验openId是否为空。
        String openId = registerUserMO.getOpenId();
        if (StringUtils.isBlank(openId)) {
            throw new BusinessException(ResultCode.COMMON_PARAMETER_ERROR,
                    "register openId is illegal,the openId is：" + openId);
        }
        // 校验手机设备的唯一性。业务：A设备注册了1号用户，2号用户就不能在A设备上进行注册。以后进行建议流程优化。
        String clientId = registerUserMO.getClientId();
        if (StringUtils.isBlank(clientId)) {
            log.error("validateRegisterInfo method clientId is blank");
        }
        int countRows = registerDeviceDBDAO.isExistClientId(clientId);
        // 第三方登录的只对微信和手机号码注册的用户进行设备唯一性校验
        if (type == 2 && countRows >= 1) {
            // 已经存在的clientId则不能进行注册。
            throw new BusinessException(ResultCode.SSO_REGISTER_CLIENTID_IS_EXISTED,
                    "register clientId is existed,the clientId is：" + clientId);
        }
        // 校验手机设备注册唯一性
        // 校验头像不能为空
        String headPortrait = registerUserMO.getHeadPortrait();
        if (StringUtils.isBlank(headPortrait)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_HEADPORTRAIT_IS_ILLEGAL,
                    "register headPortrait is illegal,the headPortrait is：" + headPortrait);
        }
        // 校验昵称
        String nickname = registerUserMO.getNickname();
        if (StringUtils.isBlank(nickname)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_NICKNAME_IS_ILLEGAL,
                    "register nickname is illegal,the nickname is：" + nickname);
        }
        /** 需要对接的关键词过滤进行判定。 **/
        // 敏感词校验并且过滤
        SensitiveWordsResult resNickname = MSensitiveWordsUtils.checkSensitiveWords(nickname);
        if (!resNickname.isStatus() && resNickname.getCode() == 1) {
            throw new BusinessException(ResultCode.SSO_REGISTER_NICKNAME_IS_ILLEGAL,
                    "register nickname is illegal,have sensitive words,the nickname is：" + nickname);
        }
        /** 需要对接银哥的关键词过滤进行判定。 **/
        // 校验生日，生日不能比当前时间大。保证年龄不能为负数
        Date birthday = registerUserMO.getBirthday();
        // 当前日期
        Date curDate = new Date();
        if (birthday.after(curDate)) {
            throw new BusinessException(ResultCode.SSO_REGISTER_BIRTHDAY_IS_ILLEGAL,
                    "register birthday is illegal,the birthday is：" + birthday);
        }
        curDate = null;
        // 校验性别只能是0或者1
        int gender = registerUserMO.getGender();
        if (gender != 0 && gender != 1) {
            throw new BusinessException(ResultCode.SSO_REGISTER_GENDER_IS_ILLEGAL,
                    "register gender is illegal,the gender is：" + gender);
        }
        // 校验情感状态
        int feelingStatus = registerUserMO.getFeelingStatus();
        if (feelingStatus <= 0) {
            throw new BusinessException(ResultCode.SSO_REGISTER_FEELINGSTATUS_IS_ILLEGAL,
                    "register feelingStatus is illegal,the feelingStatus is：" + feelingStatus);
        }

        // 将邀请码转换成小写（刘银）
        String inviterMiaoId = registerUserMO.getInviterMiaoId();
        if (StringUtils.isNotBlank(inviterMiaoId))
            registerUserMO.setInviterMiaoId(inviterMiaoId.toUpperCase());

    }

    private RegisterUserMO convertToRegisterUserRequest(ThirdPartyRegisterUserMO registerUserRequest, UserDO userDO) {
        RegisterUserMO ru = new RegisterUserMO();
        ru.setBirthday(registerUserRequest.getBirthday());
        // dengjie TODO
        ru.setCaptcha(StringUtils.EMPTY);
        ru.setClientId(registerUserRequest.getClientId());
        ru.setDeviceType(registerUserRequest.getDeviceType());
        ru.setFeelingStatus(registerUserRequest.getFeelingStatus());
        ru.setGender(registerUserRequest.getGender());
        ru.setHeadPortrait(registerUserRequest.getHeadPortrait());
        ru.setInviterMiaoId(registerUserRequest.getInviterMiaoId());
        ru.setLat(registerUserRequest.getLat());
        ru.setLon(registerUserRequest.getLon());
        // dengjie TODO
        ru.setMobile(userDO.getMobile());
        ru.setNickname(registerUserRequest.getNickname());
        ru.setOriginType(registerUserRequest.getOriginType());
        // dengjie TODO
        ru.setPassword(StringUtils.EMPTY);
        ru.setPhoneType(registerUserRequest.getPhoneType());
        ru.setRegisterAddress(registerUserRequest.getRegisterAddress());
        ru.setRegisterIp(registerUserRequest.getRegisterIp());
        ru.setSource(registerUserRequest.getSource());
        return ru;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public Result thirdPartyRegister(ThirdPartyRegisterUserMO registerUserMO) {
        validateThirdPartyRegisterInfo(registerUserMO);
        // 校验通过则进行注册流程
        // 为用户自动生成miaoId和userId
        long bgId = counterService.nextCount(CounterService.KeyType.BG_ID);
        long userId = counterService.nextCount(CounterService.KeyType.USER_ID);

        // 创建sso t_user相关信息
        UserDO userDO = new UserDO();
        userDO.setBgId(String.valueOf(bgId));
        userDO.setUserId(userId);
        // 第三方注册的时候，手机号码和密码都是miaoId。等以后绑定的时候在来填充
        userDO.setMobile("tp-" + userDO.getBgId());
        userDO.setPassword(StringUtils.EMPTY);
        // 第三方注册的时候，手机号码和密码都是空字符串。等以后绑定的时候在来填充
        userDO.setRegisterIp(registerUserMO.getRegisterIp());
        userDO.setPhoneType(registerUserMO.getPhoneType());
        userDO.setLon(registerUserMO.getLon());
        userDO.setLat(registerUserMO.getLat());
        userDO.setRegisterAddress(registerUserMO.getRegisterAddress());
        userDO.setOriginType(registerUserMO.getOriginType());
        userDO.setDeviceType(registerUserMO.getDeviceType());
        userDO.setInviterMiaoId(registerUserMO.getInviterMiaoId());
        userDO.setEnabled(UserDO.USER_ENABLED_OK);
        userDO.setLoginFrequency(0);
        userDO.setOnlineStatus(UserDO.USER_ONLINE_STATUS_OFF);
        userDBDAO.save(userDO);
        // 插入绑定第三方账号信息
        UserBindThirdPartyDO userBindThirdPartyDO = new UserBindThirdPartyDO();
        userBindThirdPartyDO.setType(registerUserMO.getType());
        userBindThirdPartyDO.setUserId(userId);
        userBindThirdPartyDO.setOpenId(registerUserMO.getOpenId());
        userBindThirdPartyDBDAO.save(userBindThirdPartyDO);
        // 插入绑定第三方账号信息
        // 保存注册设备clientId（个推）
        saveRegisterDevice(registerUserMO.getClientId());
        // 为了统一使用方法，信息对象转换后统一调用方法
        RegisterUserMO ru = convertToRegisterUserRequest(registerUserMO, userDO);
        // RPC创建用户中心系统、判断是否自动加邀请人为好友。
        // TODO dengjie 以后修改成内部接口调用
        // initializeUsercenter(userId, bgId, ru);
        // RPC创建用户中心系统、判断是否自动加邀请人为好友。

        // 初始化新注册用户表信息。用来判断是否引导认识流程。
        initNewUserInfo(userId);

        Result res = new Result(ResultCode.COMMON_SUCCESS, true);
        return res;
    }

    private void validateThirdPartyUserStatusAndPwd(UserDO user) {
        int enabled = user.getEnabled();
        int loginFrequency = user.getLoginFrequency();
        Date lastLoginTime = user.getLastLoginTime();
        // 查询系统代码，找到系统代码中的“登陆限制-登陆失败次数”，“登陆显示-登陆失败允许重新登陆时间长度”
        // 切换到redis中进行查询
        Map<String, List<SystemCodeDO>> mapConfig = systemCodeService.getSystemCodeMapWithList(
                ContextConstants.SC_LOGIN_LIMIT_FAILURE_FREQUENCY, ContextConstants.SC_LOGIN_LIMIT_RELOGIN_PERIOD);
        int limitFailureFrequency = Integer
                .valueOf(mapConfig.get(ContextConstants.SC_LOGIN_LIMIT_FAILURE_FREQUENCY).get(0).getCodeValue());
        int limitReloginPeriod = Integer
                .valueOf(mapConfig.get(ContextConstants.SC_LOGIN_LIMIT_RELOGIN_PERIOD).get(0).getCodeValue());
        if (enabled == 0) {
            // 账号被禁用
            loginFailureOp(user, ResultCode.SSO_LOGIN_USER_IS_DISABLED.getDescription(), 2, loginFrequency,
                    limitFailureFrequency);
            throw new BusinessException(ResultCode.SSO_LOGIN_USER_IS_DISABLED,
                    "user is disabled,the userId is:" + user.getUserId());
        }

        long diffTime = DateUtil.getDiff(new Date(), lastLoginTime);
        if (loginFrequency >= limitFailureFrequency && diffTime < limitReloginPeriod) {
            // 账号登陆失败次数过多被禁用
            loginFailureOp(user, ResultCode.SSO_LOGIN_USER_FAILED_NUMBER_IS_TOO_MUCH.getDescription(), 4,
                    loginFrequency, limitFailureFrequency);
            throw new BusinessException(ResultCode.SSO_LOGIN_USER_FAILED_NUMBER_IS_TOO_MUCH,
                    "the user login failed numbers is too much,the userId is:" + user.getUserId());
        }

    }

    @Override
    public Result thirdPartyLogin(int type, String openId, String clientId) {
        AssertUtil.gt(type, 0, "type must be gt zero");
        AssertUtil.isNotBlank(openId, "openId is null or blank");
        // 由于个推的问题，clientId不能获取到那么这个不能禁止用户去登陆。该记录日志。
        if (StringUtils.isBlank(clientId)) {
            log.error("user third party login error.the clientId is blank.the type is:" + type + ";the openId is:"
                    + openId);
        }
        Result result = new Result(ResultCode.COMMON_SUCCESS, true);
        // 根据type和openId查询第三方账号绑定的自有体系user信息。
        UserDO user = null;
        Long userId = userBindThirdPartyDBDAO.getUserIdByTypeAndOpenId(type, openId);
        if (userId == null || userId == 0) {
            throw new BusinessException(ResultCode.SSO_THIRD_PARTY_LOGIN_ACCOUT_IS_NOT_EXISTED,
                    "type or openId is not exist,the type is：" + type + ",openId is: " + openId);
        }
        user = userDBDAO.queryUserByUserId(userId);
        // 根据type和openId查询第三方账号绑定的自有体系user信息。

        // 用户名密码正确查询出来用户了，创建userId和token的关系并且存入缓存。暂时不设置过期时间，以后如果要做过期功能则再进行设置。
        if (user != null) {
            // 校验user的账号状态是否禁用、登陆失败次数是否超过要求等限制条件、登陆密码是否正确
            validateThirdPartyUserStatusAndPwd(user);

            final String token = TokenGenerator.generateValue();
            // 写入缓存中，采用强制覆盖的方式set进入
            userTokenCacheDAO.setUserToken(userId, token);
            // 通过更新通知系统的clientId
            // 初始化更新维护用户推送相关信息
            // TODO dengjie 以后修改成内部接口调用
            // userDeviceRPCService.createUserDeviceInfo(userId, clientId, 0);
            // 通过更新通知系统的clientId
            // 方便排查错误并且保证用户登陆状态安全，记录该用户的token钥匙在DB中，保证touchToken校验的时候缓存出现故障也能保证其校验通过提高用户体验
            saveDBInfoForLoginBiz(userId, token, clientId);
            result.setProperty("userId", userId);
            result.setProperty("token", token);
            // 新增判定该用户是否是第一次注册的新用户
            boolean isFirstRegistration = newUserService.queryNewUserByUserId(userId);
            result.setProperty("isFirstRegistration", isFirstRegistration);
            // 新增判定用户是否完成上架引导
            // boolean isFinishPutawayGuidance = newUserService.queryIsFinishPutawayGuidanceByUserId(userId);
            // result.setProperty("isFinishPutawayGuidance", isFinishPutawayGuidance);
        } else {
            throw new BusinessException(ResultCode.SSO_THIRD_PARTY_LOGIN_ACCOUT_IS_NOT_EXISTED,
                    "type or openId is illegal,the type is：" + type + ",openId is: " + openId);
        }
        return result;
    }

}
